<template>
  <div ref="fileUploadRef" @click.stop="handleFileClick" :class="{ '--biz--upload': true, disabled: disabled }">
    <input ref="fileInputRef" type="file" :disabled="disabled" style="display: none" :multiple="multiple" :accept="accept" />
    <input
      ref="directoryInputRef"
      type="file"
      :disabled="disabled"
      webkitdirectory
      mozdirectory
      odirectory
      style="display: none"
    />
    <div class="--biz-upload-dragger">
      <slot>
        <p class="--biz-icon-upload">
          <el-icon class="--biz-icon"><upload-filled /></el-icon>
        </p>
        <p class="--biz-text-upload">
          <span v-html="dynamicContent"></span>或
          <a class="--biz-text__inner" @click.stop="handleFileClick">点击上传文件</a>
          <template v-if="directory">
            或
            <a class="--biz-text__inner" style="z-index: 3" @click.stop="handleDirectoryClick"> 点击上传文件夹 </a>
          </template>
        </p>
      </slot>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from "vue";
import { uploadFilledProps, uploadEmits } from "./uploadFilled";

const porps = defineProps(uploadFilledProps);
const emit = defineEmits(uploadEmits);

const directoryInputRef = ref();
const fileUploadRef = ref();
const fileInputRef = ref();
const noDragDropSupport = ref(false);
const directory = ref<boolean>(true);
const currentCount = ref<number>(0);

function checkDragDropSupport() {
  const div = document.createElement("div");
  return "draggable" in div || ("ondragstart" in div && "ondrop" in div);
}

const dynamicContent = computed(() => {
  return noDragDropSupport.value
    ? "浏览器不支持拖拽上传"
    : `将文件${directory.value ? " / 文件夹" : ""}<span style="font-size: 16px;color: var(--el-color-primary)"> 拖到此处 </spam>`;
});

const clearCounter = () => {
  currentCount.value = 0;
};

const createQueue = (concurrency: number, fn: <T extends FileSystemFileEntry>(t: T) => Promise<T>) => {
  const queue: any[] = [];
  const runningQueue: any[] = [];
  const removeQueue = (task: any) => {
    const index = queue.findIndex((item) => item === task);
    if (index !== -1) {
      queue.splice(index, 1);
    }
  };
  const process = (dataItem: FileSystemFileEntry, getRemoveQueue?: Function) => {
    return new Promise((resolve, reject) => {
      const run = async () => {
        if (runningQueue.length >= concurrency) {
          queue.push(run);
          getRemoveQueue && getRemoveQueue(() => removeQueue(run));
          return;
        }
        runningQueue.push(run);
        try {
          const result = await fn(dataItem);
          resolve(result);
        } catch (e) {
          reject(e);
        } finally {
          runningQueue.splice(runningQueue.indexOf(run), 1);
          if (queue.length) {
            queue.shift()();
          }
        }
      };
      run();
    });
  };
  return process;
};

const readFileEntrieQueue = createQueue(20, (entery: FileSystemFileEntry) => {
  // 读取文件
  return new Promise((resolve) => {
    entery.file((file: any) => {
      resolve(file);
    });
  });
});

const readDirEntrieQueue = createQueue(20, (entery: any) => {
  return new Promise((resolve) => {
    // 读取文件夹,返回一个FileSystemDirectoryReader对象,该对象表示一个目录的内容,可以通过readEntries()方法读取目录的内容
    entery.createReader().readEntries((entries: any) => {
      resolve(entries);
    });
  });
});

const dfsForDirectory = async (item: FileSystemFileEntry | null) => {
  if (!item) return;
  if (item.isFile) {
    const file = (await readFileEntrieQueue(item)) as File;
    emit("getFiles", file);
  } else {
    // 获取文件夹中的文件entry
    const entries = (await readDirEntrieQueue(item)) as FileSystemFileEntry[];
    currentCount.value += entries.length;
    if (!porps.multiple && (entries.length > 1 || currentCount.value > 1)) {
      emit("onExceed", currentCount.value);
      return;
    }
    for (const entry of entries) {
      // 递归获取文件
      dfsForDirectory(entry);
    }
  }
};

const getFilesFromDataTransferItemList = async (list: DataTransferItemList | undefined) => {
  if (!list) return;
  const len = list.length;
  if (!porps.multiple && (len > 1 || currentCount.value > 1)) {
    emit("onExceed", len || currentCount.value);
    return;
  }
  for (let i = 0; i < len; i++) {
    // webkitGetAsEntry()方法返回一个FileSystemEntry对象，表示DataTransferItem对象的文件系统条目
    const item = list[i].webkitGetAsEntry() as FileSystemFileEntry;
    dfsForDirectory(item);
  }
};

const handleFileChange = (event: Event) => {
  const files = (event.target as HTMLInputElement).files as FileList;
  currentCount.value += files.length;
  if (!porps.multiple && (files.length > 1 || currentCount.value > 1)) {
    emit("onExceed", currentCount.value);
    return;
  }
  emit("getFiles", files);
};

const handleDragLeave = (event: Event) => {
  event.preventDefault();
  event.stopPropagation();
};

const handleDrop = (event: DragEvent) => {
  if (!porps.drag || porps.disabled) return;
  event.preventDefault();
  event.stopPropagation();
  clearCounter();
  getFilesFromDataTransferItemList(event.dataTransfer?.items);
};

onMounted(() => {
  noDragDropSupport.value = !checkDragDropSupport();
  fileInputRef.value?.addEventListener("change", handleFileChange);
  directoryInputRef.value?.addEventListener("change", handleFileChange);
  fileUploadRef.value?.addEventListener("dragleave", handleDragLeave);
  fileUploadRef.value?.addEventListener("dragover", handleDragLeave);
  fileUploadRef.value?.addEventListener("drop", handleDrop);
});

const handleFileClick = (event: Event) => {
  if (porps.disabled || event.target !== event.currentTarget) return;
  clearCounter();
  fileInputRef.value.click();
};

const handleDirectoryClick = () => {
  if (porps.disabled) return;
  clearCounter();
  directoryInputRef.value.click();
};

onUnmounted(() => {
  fileInputRef.value?.removeEventListener("change", handleFileChange);
  directoryInputRef.value?.removeEventListener("change", handleFileChange);
  fileUploadRef.value?.removeEventListener("dragleave", handleDragLeave);
  fileUploadRef.value?.removeEventListener("dragover", handleDragLeave);
  fileUploadRef.value?.removeEventListener("drop", handleDrop);
});
</script>

<style scoped lang="scss">
.--biz--upload {
  width: 100%;
  min-height: 130px;
  background-color: var(--el-fill-color-blank);
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  box-sizing: border-box;
  cursor: pointer;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  .--biz-upload-dragger {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 40px 10px;
    .--biz-icon-upload {
      .--biz-icon {
        font-size: 67px;
        color: var(--el-text-color-placeholder);
        line-height: 50px;
        margin-bottom: 16px;
      }
    }
    .--biz-text-upload {
      font-size: 14px;
      text-align: center;
      .--biz-text__inner {
        color: var(--el-color-primary);
      }
    }
  }
  &:hover {
    border-color: var(--el-color-primary);
  }
}
.disabled {
  cursor: not-allowed;
  opacity: 0.5;
  border-color: transparent !important;
  background-color: var(--el-color-info-light-8);
}
</style>
